Este es el pipeline seguido para el análisis de los metagenomas de amplicones de ITS del trabajo de Daniella Senatore, utilizando dada2. Para correrlo se requieren los paquetes dada2, ShortRead y phyloseq, así como también Qiime2. Para instalar Qiime2 podemos consultar el tutorial disponible en https://docs.qiime2.org/2020.11/install/

1 Pasos previos

dada2 requiere que los archivos que contienen las secuencias sean previamente desmultiplexadas. Para esto, hay diferentes opciones disponibles. Yo utilicé Qiime2, siguiendo los siguientes pasos (se asume que Qiime2 está instalado y accesible):

1.1 Pasos previos en la línea de comandos, utilizando Qiime

1.1.1 Comprimir el archivo fastq.

Antes de importar las secuencias a Qiime debemos comprimir el archivo utilizando el programa gzip:

Requiere:

  • archivo .fastq multiplexado.

Se obtiene: archivo .fastq.gz

gzip archivo_fastq_multiplex.fastq

1.1.2 Importar archivo con las secuencias a Qiime2

Dado que utilizaremos Qiime2 para demultiplexar los reads, el primer paso en el pipeline de Qiime es la importación.

Requiere:

  • archivo multiplexado.fastq.gz

Se obtiene:

  • archivo multiplexado.qza
qiime tools import --type MultiplexedSingleEndBarcodeInSequence  --input-path archivo_fastq_multiplex.fastq.gz --output-path multiplexed-seqs.qza

Argumentos del comando:

--input-path: path al archivo que se importa.

--output-path: nombre que se le da al archivo importado. Tiene extensión qza.

--type MultiplexedSingleEndBarcodeInSequence: indica que el archivo que se importa es multiplexado, contiene barcodes y contiene reads single end (SE).

Output:

Saved SampleData[SequencesWithQuality] to: demultiplex_seqs.qza
Saved MultiplexedSingleEndBarcodeInSequence to: unmatched_barcode.qza

1.1.3 Demultiplexar.

Este paso se realizó según las instrucciones disponibles en https://forum.qiime2.org/t/demultiplexing-and-trimming-adapters-from-reads-with-q2-cutadapt/2313.

Requiere:

  • archivo mapping file: que contiene una tabla con columnas separadas por tabulaciones donde se incluyan los nombres de las secuencias y los barcodes.
  • nombre de la columna del mapping file que contiene los barcodes.
  • path al archivo .qza obtenido en el paso anterior.
  • el nombre que tendrá el directorio que contendrá los reads demultiplexados.

Se obtiene:

  • archivos con los reads de cada muestra por separado en el directorio especificado.
qiime cutadapt demux-single --i-seqs multiplexed-seqs.qza --m-barcodes-file mappingfile.txt --m-barcodes-column BarcodeSequence --o-per-sample-sequences demultiplex_seqs.qza --o-untrimmed-sequences unmatched_barcode.qza --output-dir demultiplex

Argumentos del comando:

cutadapt demux-single: se ejecuta el programa Cutadapt para demultiplexar secuencias SE.

--o-untrimmed-sequences: locación de las secuencias que no matchearon con ningún barcode.

--i-seqs: nombre del archivo importado (qza), que se generó en el paso anterior.

--m-barcodes-file: path al mapping file.

--m-barcodes-column: nombre de la columna del mapping file que contiene la secuencias de los barcodes.

1.1.4 Remover los barcodes

Una vez demultiplexados, deben removerse los barcodes de los reads. Para esto volveremos a utilizar el plug-in Cutadapt de Qiime.

Requiere:

  • path a las secuencias demultiplexadas del paso anterior

Se obtiene:

  • Reads multiplexados y trimmeados en archivo .qza
qiime cutadapt trim-single --i-demultiplexed-sequences demultiplex_seqs.qza --o-trimmed-sequences trimmed-demult_seqs.qza

Argumentos del comando:

cutadapt trim-single: se ejecuta el programa Cutadapt para trimmear secuencias de adaptadores o barcodes en reads SE.

--i-demultiplexed-sequences: archivo .qza que contiene los reads demultiplexados obtenidos en el paso anterior.

--o-trimmed-sequences: nombre del archivo de salida (qza) que contiene los reads demultiplexados y trimmeados.

1.1.5 Exportar los reads demultiplexados sin barcodes

Hasta ahora venimos trabajando con Qiime2, por lo tanto todos los archivos que se generan durante el procesamiento de las muestras está almacenado en artefactos. Para continuar el procesamiento de los datos en dada2 debemos transformarlos en fastq. Para esto exportamos los datos contenidos en archivos .qza.

Requiere:

  • archivos a exportar

Se obtiene:

  • archivos en formato apropiado para continuar utilizando otro software
mkdir demulti
qiime tools extract --input-path  trimmed-demult_seqs.qza --output-path trim-demult

Argumentos:

--input-path: path del archivo qza a exportar

--output-path: nombre de la carpeta que contiene los datos exportados

1.2 Procesamiento de datos en R utilizando dada2

En este punto contamos con los reads desmultiplexados y sin barcodes, y continuaremos su procesamiento en dada2. Si los paquetes necesarios no están instalados, pueden obtenerse ejecutando:

# Librerías auxiliares
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.2     ✓ purrr   0.3.4
✓ tibble  3.0.4     ✓ dplyr   1.0.2
✓ tidyr   1.1.2     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.0
── Conflicts ───────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::collapse()   masks Biostrings::collapse(), IRanges::collapse()
x dplyr::combine()    masks Biobase::combine(), BiocGenerics::combine()
x purrr::compact()    masks XVector::compact()
x purrr::compose()    masks ShortRead::compose()
x dplyr::count()      masks matrixStats::count()
x dplyr::desc()       masks IRanges::desc()
x tidyr::expand()     masks S4Vectors::expand()
x dplyr::filter()     masks stats::filter()
x dplyr::first()      masks GenomicAlignments::first(), S4Vectors::first()
x dplyr::id()         masks ShortRead::id()
x dplyr::lag()        masks stats::lag()
x dplyr::last()       masks GenomicAlignments::last()
x ggplot2::Position() masks BiocGenerics::Position(), base::Position()
x purrr::reduce()     masks GenomicRanges::reduce(), IRanges::reduce()
x dplyr::rename()     masks S4Vectors::rename()
x dplyr::slice()      masks XVector::slice(), IRanges::slice()
x tibble::view()      masks ShortRead::view()

Una vez instalados los paquetes requeridos se cargan en la sesión de R, utilizando la función library():

library(dada2)
Loading required package: Rcpp
library(ShortRead)
library(phyloseq)

1.2.1 Especificar la ubicación de los datos procesados:

En el paso anterior exportamos los datos procesados por Qiime. Ahora guardamos en una variable llamada path la ubicación de los archivos exportados. En este caso, los archivos se encuenctran en el directorio trimm_demult:

path <- "trimm_demult"

Ahora creamos el objeto fnFs, que contiene los path a los archivos fastq demultiplexados y sin barcodes, especificando un patrón que tengan en común los nombres de los archivos. En este caso ese patrón es __001.fastq.gz, ya que todos los archivos a analizar contienen este sufijo.

fnFs <- sort(list.files(path, pattern = "_001.fastq.gz", full.names = TRUE))

1.2.2 Eliminar Ns y Trimmear adaptadores.

Los reads procesados anteriormente aún contienen las secuencias adaptadoras que se adicionaron a los fragmentos de ADN durante la creación de las librerías para la secuenciación, que debemos conocer de antemano. Además, para que dada2 funcione no puede haber ningúna base indeterminada (N) en las secuencias.

Primero, creamos un directorio donde vamos a guardar los reads sin Ns, y guardamos la ruta a los archivos que se crearán en un objeto:

dir.create("filtN")
fnFs.filtN <- file.path(path, "filtN", basename(fnFs)) 
fnFs.filtN
 [1] "trimm_demult/filtN/A_AGGCAATTGC_L001_R1_001.fastq.gz"  
 [2] "trimm_demult/filtN/B_TTAGTCGGAC_L001_R1_001.fastq.gz"  
 [3] "trimm_demult/filtN/C_CAGATCCATC_L001_R1_001.fastq.gz"  
 [4] "trimm_demult/filtN/D_TCGCAATTAC_L001_R1_001.fastq.gz"  
 [5] "trimm_demult/filtN/E_TTCGAGACGC_L001_R1_001.fastq.gz"  
 [6] "trimm_demult/filtN/F_TGCCACGAAC_L001_R1_001.fastq.gz"  
 [7] "trimm_demult/filtN/G_AACCTCATTC_L001_R1_001.fastq.gz"  
 [8] "trimm_demult/filtN/H_CCTGAGATAC_L001_R1_001.fastq.gz"  
 [9] "trimm_demult/filtN/I_TTACAACCTC_L001_R1_001.fastq.gz"  
[10] "trimm_demult/filtN/J_AACCATCCGC_L001_R1_001.fastq.gz"  
[11] "trimm_demult/filtN/K_ATCCGGAATC_L001_R1_001.fastq.gz"  
[12] "trimm_demult/filtN/L_TCGACCACTC_L001_R1_001.fastq.gz"  
[13] "trimm_demult/filtN/M_CGAGGTTATC_L001_R1_001.fastq.gz"  
[14] "trimm_demult/filtN/N_TCCAAGCTGC_L001_R1_001.fastq.gz"  
[15] "trimm_demult/filtN/O_TCTTACACAC_L001_R1_001.fastq.gz"  
[16] "trimm_demult/filtN/P_TTCTCATTGAAC_L001_R1_001.fastq.gz"
[17] "trimm_demult/filtN/Q_TCGCATCGTTC_L001_R1_001.fastq.gz" 
[18] "trimm_demult/filtN/R_TAAGCCATTGTC_L001_R1_001.fastq.gz"

Ahora utilizaremos la función filterAndTrim para eliminar las Ns de las secuencias, y especificamos los path a los reads demultiplexados sin barcodes (guardados en fnFs), y el path a los reads sin Ns que se crearán.

filterAndTrim(fnFs, fnFs.filtN, maxN = 0, multithread = TRUE)
Creating output directory: trimm_demult/filtN

maxN = 0 indica que el número máximo de Ns en cada secuencia es 0

multithreads = TRUE indica que se pueden utilizar varios nucleos en el proceso.

Ahora se eliminarán los adaptadores de los reads. Para esto, primero cargamos la secuencia del adaptador a al objeto FWD para luego obtener la secuencia en todas las orientaciones posibles: directa, inversa, complementaria e inversa complementaria, aplicando una función que llamaremos allOrients:

FWD <- "GATCTTGGTCATTTAGAGGAAGTA" # secuencia del adaptador

allOrients <- function(primer) {
    # Create all orientations of the input sequence
    require(Biostrings)
    dna <- DNAString(primer)  # The Biostrings works w/ DNAString objects rather than character vectors
    orients <- c(Forward = dna, Complement = complement(dna), Reverse = reverse(dna), 
        RevComp = reverseComplement(dna))
    return(sapply(orients, toString))  # Convert back to character vector
}

FWD.orients <- allOrients(FWD)
FWD.orients
                   Forward                 Complement                    Reverse 
"GATCTTGGTCATTTAGAGGAAGTA" "CTAGAACCAGTAAATCTCCTTCAT" "ATGAAGGAGATTTACTGGTTCTAG" 
                   RevComp 
"TACTTCCTCTAAATGACCAAGATC" 

Ahora contaremos cuántas veces se encuentran las secuencias de los adaptadores en los reads, aplicando una función que llamaremos primerHits.

primerHits <- function(primer, fn) {
  # Counts number of reads in which the primer is found
  nhits <- vcountPattern(primer, sread(readFastq(fn)), fixed = FALSE)
  return(sum(nhits > 0))
}

rbind(FWD.ForwardReads = sapply(FWD.orients, primerHits, fn = fnFs.filtN[[1]]))
                 Forward Complement Reverse RevComp
FWD.ForwardReads  191594          0       0       0

Se obtuvo una tabla que indica cuantas veces se encontraron las secuencias del adaptador en cada orientación posible.

Para efectivamente eliminar las secuencias adaptadoras, utilizaremos cutadapt, que debe estar instalado en el sistema. Como luego deberemos indicar la ubicación del ejecutable de cutadapt, lo guardaremos en un objeto:

cutadapt <- "/usr/local/bin/cutadapt"

Para ver si podemos eecutar cutadapt podemos correr el siguiente comando, que si no da error significa que el ejecutable de cutadapt funciona correctamente:

system2(cutadapt, args = "--version")

Crearemos un directorio donde se guardarán los reads sin adaptadores:

path.cut <- "demulti/cutadapt"
dir.create(path.cut)
cutFs <- sort(list.files(path.cut, pattern = "_1.fastq.gz", full.names = TRUE))

Finalmente, para eliminar los adaptadores de los reads utilizaremos:

R1.flags <- paste("-g", FWD, "-m 10")

for(i in seq_along(fnFs)) {
  system2(cutadapt, args = c(R1.flags, "-n", 2, # -n 2 required to remove FWD and REV from reads
                             "-o", fnFs.cut[i], # output files
                             fnFs.filtN[i])) # input files
}

-m 10 indica que se descarten reads menores a 10 bases

Para saber si quedaron reads sin eliminar adaptadores hacemos:

rbind(FWD.ForwardReads = sapply(FWD.orients, primerHits, fn = fnFs.cut[[1]]))
                 Forward Complement Reverse RevComp
FWD.ForwardReads       0          0       0       0

Todas las columnas con 0 indica que no se detectaron adaptadores en los reads.

1.2.3 Control de calidad de los reads

Antes de realizar el análisis del metagenoma debemos asegurarnos de trabajar con datos de buena calidad. Para esto, utilizaremos la función filterAndTrim, aplicándola sobre los reads filtrados (sin adaptadores) obtenidos en el paso anterior. Primero se generarán los nombres de los archivos trimmeados y luego se aplica la función. Para los datos de IonTorrent es necesario agregar el parámetro trimLeft=15, que elimina las primeras 15 bases de los reads.

Paso: trimming de los reads por calidad.

Requerimientos:

  • reads sin adaptadores en archivos fastq o fastqc
  • nombres que tomarán los archivos trimmeados

Se obtiene:

  • reads filtrados por calidad:

    • trimLeft = 15: se eliminan las primeras 15 bases
    • maxLen = 400: se eliminan las bases más allá de la posición 400
    • maxN = 0: máximo de N en los reads es 0
    • compress = TRUE: se comprimirán los archivos trimmeados

1.2.3.1 Observar la calidad de los reads pre-filtrado

Para determinar los parámetros que utilizaremos en el filtrado debemos observar los gráficos de calidad de reads por posición.

# Formato del nombre de los reads
cutFs <- sort(list.files(path.cut, pattern = "_001.fastq.gz", full.names = TRUE))
get.sample.name <- function(fname) strsplit(basename(fname), "_")[[1]][1]
sample.names <- unname(sapply(cutFs, get.sample.name))
head(sample.names)

plotQualityProfile(cutFs)

Plot obtenido a partr de los reads previo al filtrado de calidad. En este caso la calidad baja considerablemente a partir de la posición 400 aproximadamente. La curva roja indica el porcentaje de reads que llega a la longitud dada en el eje x. En el plot se observa que en general la calidad de los reads baja a partir de la posición 400. Según la curva roja, la proporción de reads mayores a esta longitud es baja, por lo que no perderíamos mucha información al trimmear estas bases.

1.2.3.2 Filtrar los reads

filtFs <- file.path(path.cut, "filtered", basename(cutFs))

out <- filterAndTrim(fwd = cutFs, # archivos para filtrar
                     filt = filtFs, # nombre de los filtrados
                     trimLeft = 15, # específico para IonTorrent: elimina las primeras 15 bases de cada read
                     maxN = 0, # No puede haber Ns
                     minLen = 50, # Longitud mínima de los reads filtrados
                     maxLen = 400, # Longitud máxima de los reads filtrados
                     compress = TRUE, multithread = TRUE, 
                     maxEE = 2, verbose = T)
plotQualityProfile(filtFs)

Plot obtenido a partir de los reads posterior al filtrado de calidad. En este caso la calidad no se ha visto demasiado afectada luego del filtrado realizado. Podrían utilizarse parámetros más robustos, como por ejemplo, eliminar las bases más allá de la posición 400.

A continuación se muestra una tabla que muestra el número de reads previo y post filtrado:

reads.in reads.out
A_AGGCAATTGC_L001_R1_001.fastq.gz 197989 137116
B_TTAGTCGGAC_L001_R1_001.fastq.gz 164671 127883
C_CAGATCCATC_L001_R1_001.fastq.gz 188388 139982
D_TCGCAATTAC_L001_R1_001.fastq.gz 329482 144849
E_TTCGAGACGC_L001_R1_001.fastq.gz 240860 183122
F_TGCCACGAAC_L001_R1_001.fastq.gz 281451 199745
G_AACCTCATTC_L001_R1_001.fastq.gz 239688 161239
H_CCTGAGATAC_L001_R1_001.fastq.gz 153471 123224
I_TTACAACCTC_L001_R1_001.fastq.gz 376469 273191
J_AACCATCCGC_L001_R1_001.fastq.gz 276075 215029
K_ATCCGGAATC_L001_R1_001.fastq.gz 213569 172837
L_TCGACCACTC_L001_R1_001.fastq.gz 248175 167810
M_CGAGGTTATC_L001_R1_001.fastq.gz 243272 194246
N_TCCAAGCTGC_L001_R1_001.fastq.gz 243203 195955
O_TCTTACACAC_L001_R1_001.fastq.gz 236879 145163
P_TTCTCATTGAAC_L001_R1_001.fastq.gz 300565 237256
Q_TCGCATCGTTC_L001_R1_001.fastq.gz 147184 113679
R_TAAGCCATTGTC_L001_R1_001.fastq.gz 261204 206026

1.2.4 Errores esperados y observados

dada2 tiene una función para aprender los errores de los reads. plotErrors es una función que permite ver los errores esperados en los reads y los observados.

errF <- learnErrors(filtFs2, multithread = TRUE, verbose = 2)
plotErrors(errF, nominalQ = TRUE) # No quedan como en la figura del Tutorial

Los resultados esperados para este punto se muestran a continuación:

plotErrors: resultados esperados (Segun el Tutorial)

1.2.5 Desreplicación

Vamos a eliminar todos los reads repetidos, que aportan información redundante. Este proceso se llama “desreplicar”. Para esto ejecutamos:

derepFs <- derepFastq(filtFs, verbose = TRUE)
names(derepFs) <- sample.names

1.2.6 Inferencia

En este paso es donde dada2 hace su magia. Los detalles del algoritmo pueden consultarse en el paper del paquete: https://www.nature.com/articles/nmeth.3869#methods.

1.2.6.1 Ejecutar el algoritmo dada

La función dada se aplica sobre los reads limpios, filtrados y trimmeados sin duplicados.

Paso: eliminación de ruido, inferencia de variantes

Requiere:

  • reads filtrados, limpios, desreplicados
  • objeto que contiene los resultados de aplicar la función learnErrors()

Se obtiene: una lista con tantos elementos como muestras, donde cada elemento es un objeto de clase dada que contiene los resultados de aplicar la función dada

dadaFs <- dada(derepFs, 
               err = errF, 
               multithread = TRUE,
               HOMOPOLYMER_GAP_PENALTY = -1, 
               BAND_SIZE = 32)

multithread = TRUE indica que se utulizarán varios núcleos en paralelo.

HOMOPOLYMER_GAP_PENALTY = -1 está recomendado para datos de Ion Torrent. Se refiere al costo de los gaps en regiones homopoliméricas de más de 3 bases repetidas. Si se deja como NULL estos gaps se tratan igual a los normales

BAND_SIZE = 32 está recomendado para datos de Ion Torrent. Cuando se setea se realiza alineamientos pareados globales (Needleman-Wunsch) por bandas. Las bandas restringen el número cumulativo neto de inserciones en una secuencua contra la otra. El valor por defecto es 16, pero cuando se aplica a datos de pirosecuenciación, con altas tasas de incorporación de indels, debe incrementarse.

1.2.7 Construir tabla de secuencias

Esta función construye una tabla de secuencias similar a la OTU table, que contiene una fila para cada muestra y una columna para cada secuencia única para todas las muestras. Los nombres de las columnas contienen las secuencias encontradas.

seqtab <- makeSequenceTable(dadaFs)
dim(seqtab)

1.2.8 Eliminar quimeras

En este paso se eliminan las quimeras.

seqtab.nochim <- removeBimeraDenovo(seqtab, 
                                    method = "consensus", 
                                    multithread = TRUE, verbose = TRUE)

Al elegir el método consensus, las muestras en la tabla de secuencias son chequeadas independientemente, y se construye una desición consensuada para cada variante de las secuencias.

This function implements a table-specific version of de novo bimera detection. In short, bimeric sequences are flagged on a sample-by-sample basis. Then, a vote is performed for each sequence across all samples in which it appeared. If the sequence is flagged in a sufficiently high fraction of samples, it is identified as a bimera. A logical vector is returned, with an entry for each sequence in the table indicating whether it was identified as bimeric by this consensus procedure.

1.3 Evolución de los reads a través del pipeline

En este paso, que puede realizarse en cualquier punto del pipeline, vemos cómo ha variado el número de reads a traves de los diferentes pasos del pipeline. Se produce una tabla con tantas filas como muestras y con las columnas correspondientes a los pasos del pipeline: filtrado, denoise y remoción de quimeras.

track %>% 
  as.data.frame() %>% 
  mutate(porcent_filt = 100-(filtered/input*100)) %>% 
  pull(porcent_filt) %>% 
  summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  19.07   21.08   23.37   26.92   30.32   56.04 

Vemos que en el filtrado se descarta la mayor proporción de los reads, entre 19 y 56% de los reads de partida.

1.4 Asignación taxonómica

Para la asignación taxonómica utilizaremos la base de datos UNITE, obtenida desde https://unite.ut.ee. Esto se logra con la función assignTaxonomy():

Requiere:

  • secuencias no quiméricas
  • archivo con las secuencias de referencia en formato fasta

Se obtiene:

  • Una tabla que contiene como nombres de filas las secuencias y en las columnas la taxonomía inferida, donde en cada columna se define el nivel alcanzado
head(taxa.print)
     Kingdom    Phylum                 Class                   Order              
[1,] "k__Fungi" "p__Mortierellomycota" "c__Mortierellomycetes" "o__Mortierellales"
[2,] "k__Fungi" "p__Ascomycota"        "c__Dothideomycetes"    "o__Pleosporales"  
[3,] "k__Fungi" "p__Ascomycota"        "c__Sordariomycetes"    "o__Hypocreales"   
[4,] "k__Fungi" "p__Ascomycota"        "c__Sordariomycetes"    "o__Coniochaetales"
[5,] "k__Fungi" "p__Basidiomycota"     "c__Tremellomycetes"    "o__Filobasidiales"
[6,] "k__Fungi" "p__Ascomycota"        "c__Sordariomycetes"    "o__Hypocreales"   
     Family               Genus              Species          
[1,] "f__Mortierellaceae" "g__Mortierella"   "s__elongata"    
[2,] "f__Didymellaceae"   "g__Epicoccum"     "s__thailandicum"
[3,] "f__Nectriaceae"     "g__Fusarium"      NA               
[4,] "f__Coniochaetaceae" NA                 NA               
[5,] "f__Piskurozymaceae" "g__Solicoccozyma" "s__phenolica"   
[6,] "f__Nectriaceae"     "g__Fusarium"      "s__kyushuense"  
head(taxa.print)

2 Análisis del metagenoma utilizando Phyloseq

2.1 Resumen de los pasos seguidos hasta ahora:

Antes de continuar, veremos el proceso que hemos transitado:

  1. Preprocesamiento de reads
    1.1. utilizamos Qiime2 para desmultiplexar y eliminar barcodes
    1.2. utilizando dada2:
    1.2.1. eliminamos Ns
    1.2.2. eliminamos secuencias adaptadoras
    1.2.3. eliminamos bases y reads de baja calidad
  2. Procesamiento de los reads
    2.1 Utilizamos dada2
    2.1.1. Desreplicamos para eliminar información redundante
    2.1.2. Utilizamos la función dada para eliminar ruido e inferir las secuencas diferentes
  3. Asignación taxonómica
    3.1 Utilizamos una función de dada2 para asignar cada secuencia a un taxón, utilizando una base de datos apropiada como referencia

2.2 Obtención de las tablas de resultados de los metagenomas

Utilizaremos funciones implentadas en el paquete phyloseq para obtener las tablas necesarias para visualizar los resultados del metagenoma utilizando Shaman: https://shaman.pasteur.fr

Alternativamente puede utilizarse phyloseq para continuar con los análisis y obtener visualizaciones del metagenoma, pero Shaman es más sencillo e intuitivo.

2.2.1 Obtención de la OTU table:

Antes de obtener la OTU table, vamos a construir un data frame que contiene los metadatos de cada muestra:

samples.out <- rownames(seqtab.nochim)
samdf <- read.table("mappingfiledaniellaits_corrected-2.txt", header = T)
samdf <- samdf[1:18,c(1,5,6)]
rownames(samdf) <- samples.out

Ahora, estamos en condiciones de crear un objeto phyloseq que contiene la OTU table, los metadatos y la taxa table:

ps <- phyloseq(otu_table(seqtab.nochim, taxa_are_rows=FALSE), 
               sample_data(samdf), 
               tax_table(taxa))
ps

Para construir la taxa table y la OTU table utilizamos las funciones tax_table() y otu_table() respectivamente a partir del objeto phyloseq.

otutable <- otu_table(ps)
otu.table_df <- as.data.frame(otu.table)
rownames(otu.table_df) <- sample.names

taxatable <- tax_table(ps)
taxa.table_df <- as.data.frame(taxatable)

Para exportar las tablas para visualar los resultados en Shaman utilizamos la función write.table sobre las tablas producidas en el paso anterior:

write.table(t(otu.table_df), file = "otu_table.tsv", sep = "\t", quote = F)
write.table(taxa.table_df, "taxa_table.tsv", sep = "\t", quote = F)

2.2.2 Obtención de la tabla de metadatos

Shaman también requiere una tabla de metadatos para realizar las comparaciones entre muestras. Para esto debemos eliminar de la tabla de metadatos todos las variables que contengan información redundante, que contengan datos que se obtienen a partir de transformaciones lineales de otras variables, o que no se utilicen para contrastar las muestras. En este caso elimino las columnas LinkerPrimerSequence, BarcodeSequence, Tiempo y Description, y exporto la tabla resultante:

mf <- read.table("mappingfiledaniellaits_corrected-2.txt", header = T) %>% 
  select(-LinkerPrimerSequence, -BarcodeSequence, -Tiempo, -Description)
write.table(mf, sep = "\t", "mf.tsv")

2.2.3 Para terminar

Aquí finaliza este pipeline, donde se obtuvieron:

  • OTU table
  • taxa table
  • tabla de metadatos

Estas tablas, exportadas como archivos de texto con columnas separadas por tabulaciones puede ser importada en Shaman y analizarse y visualizarse los metagenomas de las muestras, además de realizar comparaciones.

LS0tCnRpdGxlOiAiQW7DoWxpc2lzIGRlIG1ldGFnZW5vbWFzIGRlIElUUyBjb24gZGF0b3MgZGUgSW9uVG9ycmVudCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogIGh0bWxfZG9jdW1lbnQ6IAogICAgdG9jOiB5ZXMKLS0tCgpFc3RlIGVzIGVsIHBpcGVsaW5lIHNlZ3VpZG8gcGFyYSBlbCBhbsOhbGlzaXMgZGUgbG9zIG1ldGFnZW5vbWFzIGRlIGFtcGxpY29uZXMgZGUgSVRTIGRlbCB0cmFiYWpvIGRlIERhbmllbGxhIFNlbmF0b3JlLCB1dGlsaXphbmRvIGRhZGEyLgpQYXJhIGNvcnJlcmxvIHNlIHJlcXVpZXJlbiBsb3MgcGFxdWV0ZXMgZGFkYTIsIFNob3J0UmVhZCB5IHBoeWxvc2VxLCBhc8OtIGNvbW8gdGFtYmnDqW4gUWlpbWUyLiBQYXJhIGluc3RhbGFyIFFpaW1lMiBwb2RlbW9zIGNvbnN1bHRhciBlbCB0dXRvcmlhbCBkaXNwb25pYmxlIGVuIGh0dHBzOi8vZG9jcy5xaWltZTIub3JnLzIwMjAuMTEvaW5zdGFsbC8KCiMgUGFzb3MgcHJldmlvcwoKZGFkYTIgcmVxdWllcmUgcXVlIGxvcyBhcmNoaXZvcyBxdWUgY29udGllbmVuIGxhcyBzZWN1ZW5jaWFzIHNlYW4gcHJldmlhbWVudGUgZGVzbXVsdGlwbGV4YWRhcy4gUGFyYSBlc3RvLCBoYXkgZGlmZXJlbnRlcyBvcGNpb25lcyBkaXNwb25pYmxlcy4gWW8gdXRpbGljw6kgUWlpbWUyLCBzaWd1aWVuZG8gbG9zIHNpZ3VpZW50ZXMgcGFzb3MgKHNlIGFzdW1lIHF1ZSBRaWltZTIgZXN0w6EgaW5zdGFsYWRvIHkgYWNjZXNpYmxlKToKCiMjIFBhc29zIHByZXZpb3MgZW4gbGEgbMOtbmVhIGRlIGNvbWFuZG9zLCB1dGlsaXphbmRvIFFpaW1lCgojIyMgQ29tcHJpbWlyIGVsIGFyY2hpdm8gZmFzdHEuCgpBbnRlcyBkZSBpbXBvcnRhciBsYXMgc2VjdWVuY2lhcyBhIFFpaW1lIGRlYmVtb3MgY29tcHJpbWlyIGVsIGFyY2hpdm8gdXRpbGl6YW5kbyBlbCBwcm9ncmFtYSBgZ3ppcGA6CgoqKlJlcXVpZXJlOioqCgotIGFyY2hpdm8gLmZhc3RxIG11bHRpcGxleGFkby4KCioqU2Ugb2J0aWVuZToqKiBhcmNoaXZvIC5mYXN0cS5neiAgCgpgYGB7YmFzaH0KZ3ppcCBhcmNoaXZvX2Zhc3RxX211bHRpcGxleC5mYXN0cQpgYGAKCiMjIyBJbXBvcnRhciBhcmNoaXZvIGNvbiBsYXMgc2VjdWVuY2lhcyBhIFFpaW1lMiAgCgpEYWRvIHF1ZSB1dGlsaXphcmVtb3MgUWlpbWUyIHBhcmEgZGVtdWx0aXBsZXhhciBsb3MgcmVhZHMsIGVsIHByaW1lciBwYXNvIGVuIGVsICpwaXBlbGluZSogZGUgUWlpbWUgZXMgbGEgaW1wb3J0YWNpw7NuLiAgCgoqKlJlcXVpZXJlOioqCgotIGFyY2hpdm8gbXVsdGlwbGV4YWRvLmZhc3RxLmd6ICAKCioqU2Ugb2J0aWVuZToqKgoKLSBhcmNoaXZvIG11bHRpcGxleGFkby5xemEgCgpgYGB7YmFzaH0KcWlpbWUgdG9vbHMgaW1wb3J0IC0tdHlwZSBNdWx0aXBsZXhlZFNpbmdsZUVuZEJhcmNvZGVJblNlcXVlbmNlICAtLWlucHV0LXBhdGggYXJjaGl2b19mYXN0cV9tdWx0aXBsZXguZmFzdHEuZ3ogLS1vdXRwdXQtcGF0aCBtdWx0aXBsZXhlZC1zZXFzLnF6YQpgYGAKCioqQXJndW1lbnRvcyBkZWwgY29tYW5kbzoqKgoKYC0taW5wdXQtcGF0aGA6IHBhdGggYWwgYXJjaGl2byBxdWUgc2UgaW1wb3J0YS4KCmAtLW91dHB1dC1wYXRoYDogbm9tYnJlIHF1ZSBzZSBsZSBkYSBhbCBhcmNoaXZvIGltcG9ydGFkby4gVGllbmUgZXh0ZW5zacOzbiBxemEuCgpgLS10eXBlIE11bHRpcGxleGVkU2luZ2xlRW5kQmFyY29kZUluU2VxdWVuY2VgOiBpbmRpY2EgcXVlIGVsIGFyY2hpdm8gcXVlIHNlIGltcG9ydGEgZXMgbXVsdGlwbGV4YWRvLCBjb250aWVuZSBiYXJjb2RlcyB5IGNvbnRpZW5lIHJlYWRzIHNpbmdsZSBlbmQgKFNFKS4KCioqT3V0cHV0OioqICAKYGBgIApTYXZlZCBTYW1wbGVEYXRhW1NlcXVlbmNlc1dpdGhRdWFsaXR5XSB0bzogZGVtdWx0aXBsZXhfc2Vxcy5xemEKU2F2ZWQgTXVsdGlwbGV4ZWRTaW5nbGVFbmRCYXJjb2RlSW5TZXF1ZW5jZSB0bzogdW5tYXRjaGVkX2JhcmNvZGUucXphCmBgYAoKIyMjIERlbXVsdGlwbGV4YXIuICAKCkVzdGUgcGFzbyBzZSByZWFsaXrDsyBzZWfDum4gbGFzIGluc3RydWNjaW9uZXMgZGlzcG9uaWJsZXMgZW4gaHR0cHM6Ly9mb3J1bS5xaWltZTIub3JnL3QvZGVtdWx0aXBsZXhpbmctYW5kLXRyaW1taW5nLWFkYXB0ZXJzLWZyb20tcmVhZHMtd2l0aC1xMi1jdXRhZGFwdC8yMzEzLiAgCgoqKlJlcXVpZXJlOioqCgotIGFyY2hpdm8gKm1hcHBpbmcgZmlsZSo6IHF1ZSBjb250aWVuZSB1bmEgdGFibGEgY29uIGNvbHVtbmFzIHNlcGFyYWRhcyBwb3IgdGFidWxhY2lvbmVzIGRvbmRlIHNlIGluY2x1eWFuIGxvcyBub21icmVzIGRlIGxhcyBzZWN1ZW5jaWFzIHkgbG9zIGJhcmNvZGVzLiAgCi0gbm9tYnJlIGRlIGxhIGNvbHVtbmEgZGVsIG1hcHBpbmcgZmlsZSBxdWUgY29udGllbmUgbG9zIGJhcmNvZGVzLiAgCi0gcGF0aCBhbCBhcmNoaXZvIC5xemEgb2J0ZW5pZG8gZW4gZWwgcGFzbyBhbnRlcmlvci4gIAotIGVsIG5vbWJyZSBxdWUgdGVuZHLDoSBlbCBkaXJlY3RvcmlvIHF1ZSBjb250ZW5kcsOhIGxvcyByZWFkcyBkZW11bHRpcGxleGFkb3MuICAKCioqU2Ugb2J0aWVuZToqKiAgCgotIGFyY2hpdm9zIGNvbiBsb3MgcmVhZHMgZGUgY2FkYSBtdWVzdHJhIHBvciBzZXBhcmFkbyBlbiBlbCBkaXJlY3RvcmlvIGVzcGVjaWZpY2Fkby4gICAKCmBgYHtiYXNofQpxaWltZSBjdXRhZGFwdCBkZW11eC1zaW5nbGUgLS1pLXNlcXMgbXVsdGlwbGV4ZWQtc2Vxcy5xemEgLS1tLWJhcmNvZGVzLWZpbGUgbWFwcGluZ2ZpbGUudHh0IC0tbS1iYXJjb2Rlcy1jb2x1bW4gQmFyY29kZVNlcXVlbmNlIC0tby1wZXItc2FtcGxlLXNlcXVlbmNlcyBkZW11bHRpcGxleF9zZXFzLnF6YSAtLW8tdW50cmltbWVkLXNlcXVlbmNlcyB1bm1hdGNoZWRfYmFyY29kZS5xemEgLS1vdXRwdXQtZGlyIGRlbXVsdGlwbGV4CmBgYAoKKipBcmd1bWVudG9zIGRlbCBjb21hbmRvOioqCgpgY3V0YWRhcHQgZGVtdXgtc2luZ2xlYDogc2UgZWplY3V0YSBlbCBwcm9ncmFtYSBgQ3V0YWRhcHRgIHBhcmEgZGVtdWx0aXBsZXhhciBzZWN1ZW5jaWFzIFNFLgoKYC0tby11bnRyaW1tZWQtc2VxdWVuY2VzYDogbG9jYWNpw7NuIGRlIGxhcyBzZWN1ZW5jaWFzIHF1ZSBubyBtYXRjaGVhcm9uIGNvbiBuaW5nw7puIGJhcmNvZGUuCgpgLS1pLXNlcXNgOiBub21icmUgZGVsIGFyY2hpdm8gaW1wb3J0YWRvIChxemEpLCBxdWUgc2UgZ2VuZXLDsyBlbiBlbCBwYXNvIGFudGVyaW9yLgoKYC0tbS1iYXJjb2Rlcy1maWxlYDogcGF0aCBhbCAqbWFwcGluZyBmaWxlKi4KCmAtLW0tYmFyY29kZXMtY29sdW1uYDogbm9tYnJlIGRlIGxhIGNvbHVtbmEgZGVsICptYXBwaW5nIGZpbGUqIHF1ZSBjb250aWVuZSBsYSBzZWN1ZW5jaWFzIGRlIGxvcyBiYXJjb2Rlcy4KCiMjIyBSZW1vdmVyIGxvcyBiYXJjb2RlcwoKVW5hIHZleiBkZW11bHRpcGxleGFkb3MsIGRlYmVuIHJlbW92ZXJzZSBsb3MgYmFyY29kZXMgZGUgbG9zIHJlYWRzLiBQYXJhIGVzdG8gdm9sdmVyZW1vcyBhIHV0aWxpemFyIGVsICpwbHVnLWluKiBDdXRhZGFwdCBkZSBRaWltZS4KCioqUmVxdWllcmU6KioKCi0gcGF0aCBhIGxhcyBzZWN1ZW5jaWFzIGRlbXVsdGlwbGV4YWRhcyBkZWwgcGFzbyBhbnRlcmlvcgoKKipTZSBvYnRpZW5lOioqCgotIFJlYWRzIG11bHRpcGxleGFkb3MgeSB0cmltbWVhZG9zIGVuIGFyY2hpdm8gLnF6YSAgCgpgYGB7YmFzaH0KcWlpbWUgY3V0YWRhcHQgdHJpbS1zaW5nbGUgLS1pLWRlbXVsdGlwbGV4ZWQtc2VxdWVuY2VzIGRlbXVsdGlwbGV4X3NlcXMucXphIC0tby10cmltbWVkLXNlcXVlbmNlcyB0cmltbWVkLWRlbXVsdF9zZXFzLnF6YQpgYGAKCioqQXJndW1lbnRvcyBkZWwgY29tYW5kbzoqKgoKYGN1dGFkYXB0IHRyaW0tc2luZ2xlYDogc2UgZWplY3V0YSBlbCBwcm9ncmFtYSBgQ3V0YWRhcHRgIHBhcmEgdHJpbW1lYXIgc2VjdWVuY2lhcyBkZSBhZGFwdGFkb3JlcyBvIGJhcmNvZGVzIGVuIHJlYWRzIFNFLgoKYC0taS1kZW11bHRpcGxleGVkLXNlcXVlbmNlc2A6IGFyY2hpdm8gLnF6YSBxdWUgY29udGllbmUgbG9zIHJlYWRzIGRlbXVsdGlwbGV4YWRvcyBvYnRlbmlkb3MgZW4gZWwgcGFzbyBhbnRlcmlvci4KCmAtLW8tdHJpbW1lZC1zZXF1ZW5jZXNgOiBub21icmUgZGVsIGFyY2hpdm8gZGUgc2FsaWRhIChxemEpIHF1ZSBjb250aWVuZSBsb3MgcmVhZHMgZGVtdWx0aXBsZXhhZG9zIHkgdHJpbW1lYWRvcy4KCiMjIyBFeHBvcnRhciBsb3MgcmVhZHMgZGVtdWx0aXBsZXhhZG9zIHNpbiBiYXJjb2RlcwoKSGFzdGEgYWhvcmEgdmVuaW1vcyB0cmFiYWphbmRvIGNvbiBRaWltZTIsIHBvciBsbyB0YW50byB0b2RvcyBsb3MgYXJjaGl2b3MgcXVlIHNlIGdlbmVyYW4gZHVyYW50ZSBlbCBwcm9jZXNhbWllbnRvIGRlIGxhcyBtdWVzdHJhcyBlc3TDoSBhbG1hY2VuYWRvIGVuICphcnRlZmFjdG9zKi4gUGFyYSBjb250aW51YXIgZWwgcHJvY2VzYW1pZW50byBkZSBsb3MgZGF0b3MgZW4gZGFkYTIgZGViZW1vcyB0cmFuc2Zvcm1hcmxvcyBlbiBmYXN0cS4gUGFyYSBlc3RvIGV4cG9ydGFtb3MgbG9zIGRhdG9zIGNvbnRlbmlkb3MgZW4gYXJjaGl2b3MgLnF6YS4KCioqUmVxdWllcmU6KioKCi0gYXJjaGl2b3MgYSBleHBvcnRhciAgCgoqKlNlIG9idGllbmU6KioKCi0gYXJjaGl2b3MgZW4gZm9ybWF0byBhcHJvcGlhZG8gcGFyYSBjb250aW51YXIgdXRpbGl6YW5kbyBvdHJvIHNvZnR3YXJlICAKCmBgYHtiYXNofQpta2RpciBkZW11bHRpCnFpaW1lIHRvb2xzIGV4dHJhY3QgLS1pbnB1dC1wYXRoICB0cmltbWVkLWRlbXVsdF9zZXFzLnF6YSAtLW91dHB1dC1wYXRoIHRyaW0tZGVtdWx0CmBgYAoKKipBcmd1bWVudG9zKio6CgpgLS1pbnB1dC1wYXRoYDogcGF0aCBkZWwgYXJjaGl2byBxemEgYSBleHBvcnRhcgoKYC0tb3V0cHV0LXBhdGhgOiBub21icmUgZGUgbGEgY2FycGV0YSBxdWUgY29udGllbmUgbG9zIGRhdG9zIGV4cG9ydGFkb3MKCiMjIFByb2Nlc2FtaWVudG8gZGUgZGF0b3MgZW4gUiB1dGlsaXphbmRvIGBkYWRhMmAgCgpFbiBlc3RlIHB1bnRvIGNvbnRhbW9zIGNvbiBsb3MgcmVhZHMgZGVzbXVsdGlwbGV4YWRvcyB5IHNpbiBiYXJjb2RlcywgeSBjb250aW51YXJlbW9zIHN1IHByb2Nlc2FtaWVudG8gZW4gZGFkYTIuIFNpIGxvcyBwYXF1ZXRlcyBuZWNlc2FyaW9zIG5vIGVzdMOhbiBpbnN0YWxhZG9zLCBwdWVkZW4gb2J0ZW5lcnNlIGVqZWN1dGFuZG86CgpgYGB7cn0KaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkKICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJwaHlsb3NlcSIpCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJkYWRhMiIpCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJTaG9ydFJlYWQiKQoKIyBMaWJyZXLDrWFzIGF1eGlsaWFyZXMKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKVW5hIHZleiBpbnN0YWxhZG9zIGxvcyBwYXF1ZXRlcyByZXF1ZXJpZG9zIHNlIGNhcmdhbiBlbiBsYSBzZXNpw7NuIGRlIFIsIHV0aWxpemFuZG8gbGEgZnVuY2nDs24gYGxpYnJhcnkoKWA6CgpgYGB7cn0KbGlicmFyeShkYWRhMikKbGlicmFyeShTaG9ydFJlYWQpCmxpYnJhcnkocGh5bG9zZXEpCmBgYAoKIyMjIEVzcGVjaWZpY2FyIGxhIHViaWNhY2nDs24gZGUgbG9zIGRhdG9zIHByb2Nlc2Fkb3M6IAoKRW4gZWwgcGFzbyBhbnRlcmlvciBleHBvcnRhbW9zIGxvcyBkYXRvcyBwcm9jZXNhZG9zIHBvciBRaWltZS4gQWhvcmEgZ3VhcmRhbW9zIGVuIHVuYSB2YXJpYWJsZSBsbGFtYWRhIGBwYXRoYCBsYSB1YmljYWNpw7NuIGRlIGxvcyBhcmNoaXZvcyBleHBvcnRhZG9zLiBFbiBlc3RlIGNhc28sIGxvcyBhcmNoaXZvcyBzZSBlbmN1ZW5jdHJhbiBlbiBlbCBkaXJlY3RvcmlvIGB0cmltbV9kZW11bHRgOgoKYGBge3J9CnBhdGggPC0gInRyaW1tX2RlbXVsdCIKYGBgCgpBaG9yYSBjcmVhbW9zIGVsIG9iamV0byBmbkZzLCBxdWUgY29udGllbmUgbG9zIHBhdGggYSBsb3MgYXJjaGl2b3MgZmFzdHEgZGVtdWx0aXBsZXhhZG9zIHkgc2luIGJhcmNvZGVzLCBlc3BlY2lmaWNhbmRvIHVuIHBhdHLDs24gcXVlIHRlbmdhbiBlbiBjb23Dum4gbG9zIG5vbWJyZXMgZGUgbG9zIGFyY2hpdm9zLiBFbiBlc3RlIGNhc28gZXNlIHBhdHLDs24gZXMgYF9fMDAxLmZhc3RxLmd6YCwgeWEgcXVlIHRvZG9zIGxvcyBhcmNoaXZvcyBhIGFuYWxpemFyIGNvbnRpZW5lbiBlc3RlIHN1Zmlqby4gIAoKYGBge2Jhc2h9CmZuRnMgPC0gc29ydChsaXN0LmZpbGVzKHBhdGgsIHBhdHRlcm4gPSAiXzAwMS5mYXN0cS5neiIsIGZ1bGwubmFtZXMgPSBUUlVFKSkKYGBgCgojIyMgRWxpbWluYXIgTnMgeSBUcmltbWVhciBhZGFwdGFkb3Jlcy4KCkxvcyByZWFkcyBwcm9jZXNhZG9zIGFudGVyaW9ybWVudGUgYcO6biBjb250aWVuZW4gbGFzIHNlY3VlbmNpYXMgYWRhcHRhZG9yYXMgcXVlIHNlIGFkaWNpb25hcm9uIGEgbG9zIGZyYWdtZW50b3MgZGUgQUROIGR1cmFudGUgbGEgY3JlYWNpw7NuIGRlIGxhcyBsaWJyZXLDrWFzIHBhcmEgbGEgc2VjdWVuY2lhY2nDs24sIHF1ZSBkZWJlbW9zIGNvbm9jZXIgZGUgYW50ZW1hbm8uIEFkZW3DoXMsIHBhcmEgcXVlIGRhZGEyIGZ1bmNpb25lIG5vIHB1ZWRlIGhhYmVyIG5pbmfDum5hIGJhc2UgaW5kZXRlcm1pbmFkYSAoTikgZW4gbGFzIHNlY3VlbmNpYXMuCgpQcmltZXJvLCBjcmVhbW9zIHVuIGRpcmVjdG9yaW8gZG9uZGUgdmFtb3MgYSBndWFyZGFyIGxvcyByZWFkcyBzaW4gTnMsIHkgZ3VhcmRhbW9zIGxhIHJ1dGEgYSBsb3MgYXJjaGl2b3MgcXVlIHNlIGNyZWFyw6FuIGVuIHVuIG9iamV0bzoKCmBgYHtyfQpkaXIuY3JlYXRlKCJmaWx0TiIpCmZuRnMuZmlsdE4gPC0gZmlsZS5wYXRoKHBhdGgsICJmaWx0TiIsIGJhc2VuYW1lKGZuRnMpKSAKZm5Gcy5maWx0TgpgYGAKCkFob3JhIHV0aWxpemFyZW1vcyBsYSBmdW5jacOzbiBgZmlsdGVyQW5kVHJpbWAgcGFyYSBlbGltaW5hciBsYXMgTnMgZGUgbGFzIHNlY3VlbmNpYXMsIHkgZXNwZWNpZmljYW1vcyBsb3MgcGF0aCBhIGxvcyByZWFkcyBkZW11bHRpcGxleGFkb3Mgc2luIGJhcmNvZGVzIChndWFyZGFkb3MgZW4gYGZuRnNgKSwgeSBlbCBwYXRoIGEgbG9zIHJlYWRzIHNpbiBOcyBxdWUgc2UgY3JlYXLDoW4uCgpgYGB7cn0KZmlsdGVyQW5kVHJpbShmbkZzLCBmbkZzLmZpbHROLCBtYXhOID0gMCwgbXVsdGl0aHJlYWQgPSBUUlVFKQpgYGAKCmBtYXhOID0gMGAgaW5kaWNhIHF1ZSBlbCBuw7ptZXJvIG3DoXhpbW8gZGUgTnMgZW4gY2FkYSBzZWN1ZW5jaWEgZXMgMAoKYG11bHRpdGhyZWFkcyA9IFRSVUVgIGluZGljYSBxdWUgc2UgcHVlZGVuIHV0aWxpemFyIHZhcmlvcyBudWNsZW9zIGVuIGVsIHByb2Nlc28uCgpBaG9yYSBzZSBlbGltaW5hcsOhbiBsb3MgYWRhcHRhZG9yZXMgZGUgbG9zIHJlYWRzLiBQYXJhIGVzdG8sIHByaW1lcm8gY2FyZ2Ftb3MgbGEgc2VjdWVuY2lhIGRlbCBhZGFwdGFkb3IgYSBhbCBvYmpldG8gYEZXRGAgcGFyYSBsdWVnbyBvYnRlbmVyIGxhIHNlY3VlbmNpYSBlbiB0b2RhcyBsYXMgb3JpZW50YWNpb25lcyBwb3NpYmxlczogZGlyZWN0YSwgaW52ZXJzYSwgY29tcGxlbWVudGFyaWEgZSBpbnZlcnNhIGNvbXBsZW1lbnRhcmlhLCBhcGxpY2FuZG8gdW5hIGZ1bmNpw7NuIHF1ZSBsbGFtYXJlbW9zIGBhbGxPcmllbnRzYDoKCmBgYHtyfQpGV0QgPC0gIkdBVENUVEdHVENBVFRUQUdBR0dBQUdUQSIgIyBzZWN1ZW5jaWEgZGVsIGFkYXB0YWRvcgoKYWxsT3JpZW50cyA8LSBmdW5jdGlvbihwcmltZXIpIHsKICAgICMgQ3JlYXRlIGFsbCBvcmllbnRhdGlvbnMgb2YgdGhlIGlucHV0IHNlcXVlbmNlCiAgICByZXF1aXJlKEJpb3N0cmluZ3MpCiAgICBkbmEgPC0gRE5BU3RyaW5nKHByaW1lcikgICMgVGhlIEJpb3N0cmluZ3Mgd29ya3Mgdy8gRE5BU3RyaW5nIG9iamVjdHMgcmF0aGVyIHRoYW4gY2hhcmFjdGVyIHZlY3RvcnMKICAgIG9yaWVudHMgPC0gYyhGb3J3YXJkID0gZG5hLCBDb21wbGVtZW50ID0gY29tcGxlbWVudChkbmEpLCBSZXZlcnNlID0gcmV2ZXJzZShkbmEpLCAKICAgICAgICBSZXZDb21wID0gcmV2ZXJzZUNvbXBsZW1lbnQoZG5hKSkKICAgIHJldHVybihzYXBwbHkob3JpZW50cywgdG9TdHJpbmcpKSAgIyBDb252ZXJ0IGJhY2sgdG8gY2hhcmFjdGVyIHZlY3Rvcgp9CgpGV0Qub3JpZW50cyA8LSBhbGxPcmllbnRzKEZXRCkKRldELm9yaWVudHMKYGBgCgpBaG9yYSBjb250YXJlbW9zIGN1w6FudGFzIHZlY2VzIHNlIGVuY3VlbnRyYW4gbGFzIHNlY3VlbmNpYXMgZGUgbG9zIGFkYXB0YWRvcmVzIGVuIGxvcyByZWFkcywgYXBsaWNhbmRvIHVuYSBmdW5jacOzbiBxdWUgbGxhbWFyZW1vcyBgcHJpbWVySGl0c2AuCgpgYGB7cn0KcHJpbWVySGl0cyA8LSBmdW5jdGlvbihwcmltZXIsIGZuKSB7CiAgIyBDb3VudHMgbnVtYmVyIG9mIHJlYWRzIGluIHdoaWNoIHRoZSBwcmltZXIgaXMgZm91bmQKICBuaGl0cyA8LSB2Y291bnRQYXR0ZXJuKHByaW1lciwgc3JlYWQocmVhZEZhc3RxKGZuKSksIGZpeGVkID0gRkFMU0UpCiAgcmV0dXJuKHN1bShuaGl0cyA+IDApKQp9CgpyYmluZChGV0QuRm9yd2FyZFJlYWRzID0gc2FwcGx5KEZXRC5vcmllbnRzLCBwcmltZXJIaXRzLCBmbiA9IGZuRnMuZmlsdE5bWzFdXSkpCmBgYAoKU2Ugb2J0dXZvIHVuYSB0YWJsYSBxdWUgaW5kaWNhIGN1YW50YXMgdmVjZXMgc2UgZW5jb250cmFyb24gbGFzIHNlY3VlbmNpYXMgZGVsIGFkYXB0YWRvciBlbiBjYWRhIG9yaWVudGFjacOzbiBwb3NpYmxlLgoKUGFyYSBlZmVjdGl2YW1lbnRlIGVsaW1pbmFyIGxhcyBzZWN1ZW5jaWFzIGFkYXB0YWRvcmFzLCB1dGlsaXphcmVtb3MgYGN1dGFkYXB0YCwgcXVlIGRlYmUgZXN0YXIgaW5zdGFsYWRvIGVuIGVsIHNpc3RlbWEuIENvbW8gbHVlZ28gZGViZXJlbW9zIGluZGljYXIgbGEgdWJpY2FjacOzbiBkZWwgZWplY3V0YWJsZSBkZSBjdXRhZGFwdCwgbG8gZ3VhcmRhcmVtb3MgZW4gdW4gb2JqZXRvOgoKYGBge3J9CmN1dGFkYXB0IDwtICIvdXNyL2xvY2FsL2Jpbi9jdXRhZGFwdCIKYGBgCgpQYXJhIHZlciBzaSBwb2RlbW9zIGVlY3V0YXIgY3V0YWRhcHQgcG9kZW1vcyBjb3JyZXIgZWwgc2lndWllbnRlIGNvbWFuZG8sIHF1ZSBzaSBubyBkYSBlcnJvciBzaWduaWZpY2EgcXVlIGVsIGVqZWN1dGFibGUgZGUgY3V0YWRhcHQgZnVuY2lvbmEgY29ycmVjdGFtZW50ZToKCmBgYHtyfQpzeXN0ZW0yKGN1dGFkYXB0LCBhcmdzID0gIi0tdmVyc2lvbiIpCmBgYAoKQ3JlYXJlbW9zIHVuIGRpcmVjdG9yaW8gZG9uZGUgc2UgZ3VhcmRhcsOhbiBsb3MgcmVhZHMgc2luIGFkYXB0YWRvcmVzOgoKYGBge3J9CnBhdGguY3V0IDwtICJkZW11bHRpL2N1dGFkYXB0IgpkaXIuY3JlYXRlKHBhdGguY3V0KQpjdXRGcyA8LSBzb3J0KGxpc3QuZmlsZXMocGF0aC5jdXQsIHBhdHRlcm4gPSAiXzEuZmFzdHEuZ3oiLCBmdWxsLm5hbWVzID0gVFJVRSkpCmBgYAoKRmluYWxtZW50ZSwgcGFyYSBlbGltaW5hciBsb3MgYWRhcHRhZG9yZXMgZGUgbG9zIHJlYWRzIHV0aWxpemFyZW1vczoKCmBgYHtiYXNofQpSMS5mbGFncyA8LSBwYXN0ZSgiLWciLCBGV0QsICItbSAxMCIpCgpmb3IoaSBpbiBzZXFfYWxvbmcoZm5GcykpIHsKICBzeXN0ZW0yKGN1dGFkYXB0LCBhcmdzID0gYyhSMS5mbGFncywgIi1uIiwgMiwgIyAtbiAyIHJlcXVpcmVkIHRvIHJlbW92ZSBGV0QgYW5kIFJFViBmcm9tIHJlYWRzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIi1vIiwgZm5Gcy5jdXRbaV0sICMgb3V0cHV0IGZpbGVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm5Gcy5maWx0TltpXSkpICMgaW5wdXQgZmlsZXMKfQpgYGAKCmAtbSAxMGAgaW5kaWNhIHF1ZSBzZSBkZXNjYXJ0ZW4gcmVhZHMgbWVub3JlcyBhIDEwIGJhc2VzCgpQYXJhIHNhYmVyIHNpIHF1ZWRhcm9uIHJlYWRzIHNpbiBlbGltaW5hciBhZGFwdGFkb3JlcyBoYWNlbW9zOgoKYGBge3J9CnJiaW5kKEZXRC5Gb3J3YXJkUmVhZHMgPSBzYXBwbHkoRldELm9yaWVudHMsIHByaW1lckhpdHMsIGZuID0gZm5Gcy5jdXRbWzFdXSkpCmBgYAoKVG9kYXMgbGFzIGNvbHVtbmFzIGNvbiAwIGluZGljYSBxdWUgbm8gc2UgZGV0ZWN0YXJvbiBhZGFwdGFkb3JlcyBlbiBsb3MgcmVhZHMuCgojIyMgQ29udHJvbCBkZSBjYWxpZGFkIGRlIGxvcyByZWFkcwoKQW50ZXMgZGUgcmVhbGl6YXIgZWwgYW7DoWxpc2lzIGRlbCBtZXRhZ2Vub21hIGRlYmVtb3MgYXNlZ3VyYXJub3MgZGUgdHJhYmFqYXIgY29uIGRhdG9zIGRlIGJ1ZW5hIGNhbGlkYWQuIFBhcmEgZXN0bywgdXRpbGl6YXJlbW9zIGxhIGZ1bmNpw7NuIGBmaWx0ZXJBbmRUcmltYCwgYXBsaWPDoW5kb2xhIHNvYnJlIGxvcyByZWFkcyBmaWx0cmFkb3MgKHNpbiBhZGFwdGFkb3Jlcykgb2J0ZW5pZG9zIGVuIGVsIHBhc28gYW50ZXJpb3IuIFByaW1lcm8gc2UgZ2VuZXJhcsOhbiBsb3Mgbm9tYnJlcyBkZSBsb3MgYXJjaGl2b3MgdHJpbW1lYWRvcyB5IGx1ZWdvIHNlIGFwbGljYSBsYSBmdW5jacOzbi4gClBhcmEgbG9zIGRhdG9zIGRlIElvblRvcnJlbnQgZXMgbmVjZXNhcmlvIGFncmVnYXIgZWwgcGFyw6FtZXRybyBgdHJpbUxlZnQ9MTVgLCBxdWUgZWxpbWluYSBsYXMgcHJpbWVyYXMgMTUgYmFzZXMgZGUgbG9zIHJlYWRzLiAKCioqUGFzbzoqKiB0cmltbWluZyBkZSBsb3MgcmVhZHMgcG9yIGNhbGlkYWQuICAKCioqUmVxdWVyaW1pZW50b3M6KiogICAKCi0gcmVhZHMgc2luIGFkYXB0YWRvcmVzIGVuIGFyY2hpdm9zIGZhc3RxIG8gZmFzdHFjICAKLSBub21icmVzIHF1ZSB0b21hcsOhbiBsb3MgYXJjaGl2b3MgdHJpbW1lYWRvcyAgCgoqKlNlIG9idGllbmU6KiogIAoKLSByZWFkcyBmaWx0cmFkb3MgcG9yIGNhbGlkYWQ6ICAKCiAgICAtIGB0cmltTGVmdCA9IDE1YDogc2UgZWxpbWluYW4gbGFzIHByaW1lcmFzIDE1IGJhc2VzICAKICAgIC0gYG1heExlbiA9IDQwMGA6IHNlIGVsaW1pbmFuIGxhcyBiYXNlcyBtw6FzIGFsbMOhIGRlIGxhIHBvc2ljacOzbiA0MDAKICAgIC0gYG1heE4gPSAwYDogbcOheGltbyBkZSBOIGVuIGxvcyByZWFkcyBlcyAwICAKICAgIC0gYGNvbXByZXNzID0gVFJVRWA6IHNlIGNvbXByaW1pcsOhbiBsb3MgYXJjaGl2b3MgdHJpbW1lYWRvcwoKCiMjIyMgT2JzZXJ2YXIgbGEgY2FsaWRhZCBkZSBsb3MgcmVhZHMgcHJlLWZpbHRyYWRvCgpQYXJhIGRldGVybWluYXIgbG9zIHBhcsOhbWV0cm9zIHF1ZSB1dGlsaXphcmVtb3MgZW4gZWwgZmlsdHJhZG8gZGViZW1vcyBvYnNlcnZhciBsb3MgZ3LDoWZpY29zIGRlIGNhbGlkYWQgZGUgcmVhZHMgcG9yIHBvc2ljacOzbi4gIAoKYGBge3J9CiMgRm9ybWF0byBkZWwgbm9tYnJlIGRlIGxvcyByZWFkcwpjdXRGcyA8LSBzb3J0KGxpc3QuZmlsZXMocGF0aC5jdXQsIHBhdHRlcm4gPSAiXzAwMS5mYXN0cS5neiIsIGZ1bGwubmFtZXMgPSBUUlVFKSkKZ2V0LnNhbXBsZS5uYW1lIDwtIGZ1bmN0aW9uKGZuYW1lKSBzdHJzcGxpdChiYXNlbmFtZShmbmFtZSksICJfIilbWzFdXVsxXQpzYW1wbGUubmFtZXMgPC0gdW5uYW1lKHNhcHBseShjdXRGcywgZ2V0LnNhbXBsZS5uYW1lKSkKaGVhZChzYW1wbGUubmFtZXMpCgpwbG90UXVhbGl0eVByb2ZpbGUoY3V0RnMpCmBgYAoKIVsqKlBsb3Qgb2J0ZW5pZG8gYSBwYXJ0ciBkZSBsb3MgcmVhZHMgcHJldmlvIGFsIGZpbHRyYWRvIGRlIGNhbGlkYWQuKiogRW4gZXN0ZSBjYXNvIGxhIGNhbGlkYWQgYmFqYSBjb25zaWRlcmFibGVtZW50ZSBhIHBhcnRpciBkZSBsYSBwb3NpY2nDs24gNDAwIGFwcm94aW1hZGFtZW50ZS4gTGEgY3VydmEgcm9qYSBpbmRpY2EgZWwgcG9yY2VudGFqZSBkZSByZWFkcyBxdWUgbGxlZ2EgYSBsYSBsb25naXR1ZCBkYWRhIGVuIGVsIGVqZSB4Ll0ocGxvdHF1YWxpdHlfY3V0RnMucG5nKQpFbiBlbCBwbG90IHNlIG9ic2VydmEgcXVlIGVuIGdlbmVyYWwgbGEgY2FsaWRhZCBkZSBsb3MgcmVhZHMgYmFqYSBhIHBhcnRpciBkZSBsYSBwb3NpY2nDs24gNDAwLiBTZWfDum4gbGEgY3VydmEgcm9qYSwgbGEgcHJvcG9yY2nDs24gZGUgcmVhZHMgbWF5b3JlcyBhIGVzdGEgbG9uZ2l0dWQgZXMgYmFqYSwgcG9yIGxvIHF1ZSBubyBwZXJkZXLDrWFtb3MgbXVjaGEgaW5mb3JtYWNpw7NuIGFsIHRyaW1tZWFyIGVzdGFzIGJhc2VzLgoKIyMjIyBGaWx0cmFyIGxvcyByZWFkcwoKYGBge3J9CmZpbHRGcyA8LSBmaWxlLnBhdGgocGF0aC5jdXQsICJmaWx0ZXJlZCIsIGJhc2VuYW1lKGN1dEZzKSkKCm91dCA8LSBmaWx0ZXJBbmRUcmltKGZ3ZCA9IGN1dEZzLCAjIGFyY2hpdm9zIHBhcmEgZmlsdHJhcgogICAgICAgICAgICAgICAgICAgICBmaWx0ID0gZmlsdEZzLCAjIG5vbWJyZSBkZSBsb3MgZmlsdHJhZG9zCiAgICAgICAgICAgICAgICAgICAgIHRyaW1MZWZ0ID0gMTUsICMgZXNwZWPDrWZpY28gcGFyYSBJb25Ub3JyZW50OiBlbGltaW5hIGxhcyBwcmltZXJhcyAxNSBiYXNlcyBkZSBjYWRhIHJlYWQKICAgICAgICAgICAgICAgICAgICAgbWF4TiA9IDAsICMgTm8gcHVlZGUgaGFiZXIgTnMKICAgICAgICAgICAgICAgICAgICAgbWluTGVuID0gNTAsICMgTG9uZ2l0dWQgbcOtbmltYSBkZSBsb3MgcmVhZHMgZmlsdHJhZG9zCiAgICAgICAgICAgICAgICAgICAgIG1heExlbiA9IDQwMCwgIyBMb25naXR1ZCBtw6F4aW1hIGRlIGxvcyByZWFkcyBmaWx0cmFkb3MKICAgICAgICAgICAgICAgICAgICAgY29tcHJlc3MgPSBUUlVFLCBtdWx0aXRocmVhZCA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICBtYXhFRSA9IDIsIHZlcmJvc2UgPSBUKQpwbG90UXVhbGl0eVByb2ZpbGUoZmlsdEZzKQpgYGAKCiFbKipQbG90IG9idGVuaWRvIGEgcGFydGlyIGRlIGxvcyByZWFkcyBwb3N0ZXJpb3IgYWwgZmlsdHJhZG8gZGUgY2FsaWRhZC4qKiBFbiBlc3RlIGNhc28gbGEgY2FsaWRhZCBubyBzZSBoYSB2aXN0byBkZW1hc2lhZG8gYWZlY3RhZGEgbHVlZ28gZGVsIGZpbHRyYWRvIHJlYWxpemFkby4gUG9kcsOtYW4gdXRpbGl6YXJzZSBwYXLDoW1ldHJvcyBtw6FzIHJvYnVzdG9zLCBjb21vIHBvciBlamVtcGxvLCBlbGltaW5hciBsYXMgYmFzZXMgbcOhcyBhbGzDoSBkZSBsYSBwb3NpY2nDs24gNDAwLl0ocGxvdHF1YWxpdHlfZmlsdEZzLnBuZykKCkEgY29udGludWFjacOzbiBzZSBtdWVzdHJhIHVuYSB0YWJsYSBxdWUgbXVlc3RyYSBlbCBuw7ptZXJvIGRlIHJlYWRzIHByZXZpbyB5IHBvc3QgZmlsdHJhZG86IApgYGB7cn0Ka2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhrYWJsZUV4dHJhOjprYmwob3V0KSkKICAKYGBgCgojIyMgRXJyb3JlcyBlc3BlcmFkb3MgeSBvYnNlcnZhZG9zCgpkYWRhMiB0aWVuZSB1bmEgZnVuY2nDs24gcGFyYSBhcHJlbmRlciBsb3MgZXJyb3JlcyBkZSBsb3MgcmVhZHMuIHBsb3RFcnJvcnMgZXMgdW5hIGZ1bmNpw7NuIHF1ZSBwZXJtaXRlIHZlciBsb3MgZXJyb3JlcyBlc3BlcmFkb3MgZW4gbG9zIHJlYWRzIHkgbG9zIG9ic2VydmFkb3MuCgpgYGB7cn0KZXJyRiA8LSBsZWFybkVycm9ycyhmaWx0RnMyLCBtdWx0aXRocmVhZCA9IFRSVUUsIHZlcmJvc2UgPSAyKQpwbG90RXJyb3JzKGVyckYsIG5vbWluYWxRID0gVFJVRSkgIyBObyBxdWVkYW4gY29tbyBlbiBsYSBmaWd1cmEgZGVsIFR1dG9yaWFsCmBgYAoKTG9zIHJlc3VsdGFkb3MgZXNwZXJhZG9zIHBhcmEgZXN0ZSBwdW50byBzZSBtdWVzdHJhbiBhIGNvbnRpbnVhY2nDs246CgohW3Bsb3RFcnJvcnM6IHJlc3VsdGFkb3MgZXNwZXJhZG9zIChTZWd1biBlbCBUdXRvcmlhbCldKGVycm9yZXMucG5nKQoKIyMjIERlc3JlcGxpY2FjacOzbgoKVmFtb3MgYSBlbGltaW5hciB0b2RvcyBsb3MgcmVhZHMgcmVwZXRpZG9zLCBxdWUgYXBvcnRhbiBpbmZvcm1hY2nDs24gcmVkdW5kYW50ZS4gRXN0ZSBwcm9jZXNvIHNlIGxsYW1hICJkZXNyZXBsaWNhciIuIFBhcmEgZXN0byBlamVjdXRhbW9zOgoKYGBge3J9CmRlcmVwRnMgPC0gZGVyZXBGYXN0cShmaWx0RnMsIHZlcmJvc2UgPSBUUlVFKQpuYW1lcyhkZXJlcEZzKSA8LSBzYW1wbGUubmFtZXMKYGBgCgojIyMgSW5mZXJlbmNpYQoKRW4gZXN0ZSBwYXNvIGVzIGRvbmRlIGRhZGEyIGhhY2Ugc3UgbWFnaWEuIExvcyBkZXRhbGxlcyBkZWwgYWxnb3JpdG1vIHB1ZWRlbiBjb25zdWx0YXJzZSBlbiBlbCBwYXBlciBkZWwgcGFxdWV0ZTogaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubWV0aC4zODY5I21ldGhvZHMuCgojIyMjIEVqZWN1dGFyIGVsIGFsZ29yaXRtbyBkYWRhCgpMYSBmdW5jacOzbiBgZGFkYWAgc2UgYXBsaWNhIHNvYnJlIGxvcyByZWFkcyBsaW1waW9zLCBmaWx0cmFkb3MgeSB0cmltbWVhZG9zIHNpbiBkdXBsaWNhZG9zLgoKKipQYXNvOiAqKiBlbGltaW5hY2nDs24gZGUgcnVpZG8sIGluZmVyZW5jaWEgZGUgdmFyaWFudGVzCgoqKlJlcXVpZXJlOiAqKgoKLSByZWFkcyBmaWx0cmFkb3MsIGxpbXBpb3MsIGRlc3JlcGxpY2Fkb3MgIAotIG9iamV0byBxdWUgY29udGllbmUgbG9zIHJlc3VsdGFkb3MgZGUgYXBsaWNhciBsYSBmdW5jacOzbiBgbGVhcm5FcnJvcnMoKWAKCioqU2Ugb2J0aWVuZTogKiogdW5hIGxpc3RhIGNvbiB0YW50b3MgZWxlbWVudG9zIGNvbW8gbXVlc3RyYXMsIGRvbmRlIGNhZGEgZWxlbWVudG8gZXMgdW4gb2JqZXRvIGRlIGNsYXNlICpkYWRhKiBxdWUgY29udGllbmUgbG9zIHJlc3VsdGFkb3MgZGUgYXBsaWNhciBsYSBmdW5jacOzbiBgZGFkYWAKCmBgYHtyfQpkYWRhRnMgPC0gZGFkYShkZXJlcEZzLCAKICAgICAgICAgICAgICAgZXJyID0gZXJyRiwgCiAgICAgICAgICAgICAgIG11bHRpdGhyZWFkID0gVFJVRSwKICAgICAgICAgICAgICAgSE9NT1BPTFlNRVJfR0FQX1BFTkFMVFkgPSAtMSwgCiAgICAgICAgICAgICAgIEJBTkRfU0laRSA9IDMyKQpgYGAKCmBtdWx0aXRocmVhZCA9IFRSVUVgIGluZGljYSBxdWUgc2UgdXR1bGl6YXLDoW4gdmFyaW9zIG7DumNsZW9zIGVuIHBhcmFsZWxvLgoKYEhPTU9QT0xZTUVSX0dBUF9QRU5BTFRZID0gLTFgIGVzdMOhIHJlY29tZW5kYWRvIHBhcmEgZGF0b3MgZGUgSW9uIFRvcnJlbnQuIFNlIHJlZmllcmUgYWwgY29zdG8gZGUgbG9zIGdhcHMgZW4gcmVnaW9uZXMgaG9tb3BvbGltw6lyaWNhcyBkZSBtw6FzIGRlIDMgYmFzZXMgcmVwZXRpZGFzLiBTaSBzZSBkZWphIGNvbW8gYE5VTExgIGVzdG9zIGdhcHMgc2UgdHJhdGFuIGlndWFsIGEgbG9zIG5vcm1hbGVzCgpgQkFORF9TSVpFID0gMzJgIGVzdMOhIHJlY29tZW5kYWRvIHBhcmEgZGF0b3MgZGUgSW9uIFRvcnJlbnQuIEN1YW5kbyBzZSBzZXRlYSBzZSByZWFsaXphICBhbGluZWFtaWVudG9zIHBhcmVhZG9zIGdsb2JhbGVzIChOZWVkbGVtYW4tV3Vuc2NoKSBwb3IgYmFuZGFzLiBMYXMgYmFuZGFzIHJlc3RyaW5nZW4gZWwgbsO6bWVybyBjdW11bGF0aXZvIG5ldG8gZGUgaW5zZXJjaW9uZXMgZW4gdW5hIHNlY3VlbmN1YSBjb250cmEgbGEgb3RyYS4gRWwgdmFsb3IgcG9yIGRlZmVjdG8gZXMgMTYsIHBlcm8gY3VhbmRvIHNlIGFwbGljYSBhIGRhdG9zIGRlIHBpcm9zZWN1ZW5jaWFjacOzbiwgY29uIGFsdGFzIHRhc2FzIGRlIGluY29ycG9yYWNpw7NuIGRlIGluZGVscywgZGViZSBpbmNyZW1lbnRhcnNlLgoKIyMjIENvbnN0cnVpciB0YWJsYSBkZSBzZWN1ZW5jaWFzCgpFc3RhIGZ1bmNpw7NuIGNvbnN0cnV5ZSB1bmEgdGFibGEgZGUgc2VjdWVuY2lhcyBzaW1pbGFyIGEgbGEgT1RVIHRhYmxlLCBxdWUgY29udGllbmUgdW5hIGZpbGEgcGFyYSBjYWRhIG11ZXN0cmEgeSB1bmEgY29sdW1uYSBwYXJhIGNhZGEgc2VjdWVuY2lhIMO6bmljYSBwYXJhIHRvZGFzIGxhcyBtdWVzdHJhcy4gTG9zIG5vbWJyZXMgZGUgbGFzIGNvbHVtbmFzIGNvbnRpZW5lbiBsYXMgc2VjdWVuY2lhcyBlbmNvbnRyYWRhcy4KCmBgYHtyfQpzZXF0YWIgPC0gbWFrZVNlcXVlbmNlVGFibGUoZGFkYUZzKQpkaW0oc2VxdGFiKQpgYGAKCgojIyMgRWxpbWluYXIgcXVpbWVyYXMgCgpFbiBlc3RlIHBhc28gc2UgZWxpbWluYW4gbGFzIHF1aW1lcmFzLiAKCmBgYHtyfQpzZXF0YWIubm9jaGltIDwtIHJlbW92ZUJpbWVyYURlbm92byhzZXF0YWIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY29uc2Vuc3VzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11bHRpdGhyZWFkID0gVFJVRSwgdmVyYm9zZSA9IFRSVUUpCmBgYAoKQWwgZWxlZ2lyIGVsIG3DqXRvZG8gYGNvbnNlbnN1c2AsIGxhcyBtdWVzdHJhcyBlbiBsYSB0YWJsYSBkZSBzZWN1ZW5jaWFzIHNvbiBjaGVxdWVhZGFzIGluZGVwZW5kaWVudGVtZW50ZSwgeSBzZSBjb25zdHJ1eWUgdW5hIGRlc2ljacOzbiBjb25zZW5zdWFkYSBwYXJhIGNhZGEgdmFyaWFudGUgZGUgbGFzIHNlY3VlbmNpYXMuCgo+IFRoaXMgZnVuY3Rpb24gaW1wbGVtZW50cyBhIHRhYmxlLXNwZWNpZmljIHZlcnNpb24gb2YgZGUgbm92byBiaW1lcmEgZGV0ZWN0aW9uLiBJbiBzaG9ydCwgYmltZXJpYyBzZXF1ZW5jZXMgYXJlIGZsYWdnZWQgb24gYSBzYW1wbGUtYnktc2FtcGxlIGJhc2lzLiBUaGVuLCBhIHZvdGUgaXMgcGVyZm9ybWVkIGZvciBlYWNoIHNlcXVlbmNlIGFjcm9zcyBhbGwgc2FtcGxlcyBpbiB3aGljaCBpdCBhcHBlYXJlZC4gSWYgdGhlIHNlcXVlbmNlIGlzIGZsYWdnZWQgaW4gYSBzdWZmaWNpZW50bHkgaGlnaCBmcmFjdGlvbiBvZiBzYW1wbGVzLCBpdCBpcyBpZGVudGlmaWVkIGFzIGEgYmltZXJhLiBBIGxvZ2ljYWwgdmVjdG9yIGlzIHJldHVybmVkLCB3aXRoIGFuIGVudHJ5IGZvciBlYWNoIHNlcXVlbmNlIGluIHRoZSB0YWJsZSBpbmRpY2F0aW5nIHdoZXRoZXIgaXQgd2FzIGlkZW50aWZpZWQgYXMgYmltZXJpYyBieSB0aGlzIGNvbnNlbnN1cyBwcm9jZWR1cmUuCgoKIyMgRXZvbHVjacOzbiBkZSBsb3MgcmVhZHMgYSB0cmF2w6lzIGRlbCBwaXBlbGluZSAKRW4gZXN0ZSBwYXNvLCBxdWUgcHVlZGUgcmVhbGl6YXJzZSBlbiBjdWFscXVpZXIgcHVudG8gZGVsIHBpcGVsaW5lLCB2ZW1vcyBjw7NtbyBoYSB2YXJpYWRvIGVsIG7Dum1lcm8gZGUgcmVhZHMgYSB0cmF2ZXMgZGUgbG9zIGRpZmVyZW50ZXMgcGFzb3MgZGVsIHBpcGVsaW5lLiBTZSBwcm9kdWNlIHVuYSB0YWJsYSBjb24gdGFudGFzIGZpbGFzIGNvbW8gbXVlc3RyYXMgeSBjb24gbGFzIGNvbHVtbmFzIGNvcnJlc3BvbmRpZW50ZXMgYSBsb3MgcGFzb3MgZGVsIHBpcGVsaW5lOiBmaWx0cmFkbywgZGVub2lzZSB5IHJlbW9jacOzbiBkZSBxdWltZXJhcy4KCmBgYHtyfQpnZXROIDwtIGZ1bmN0aW9uKHgpIHN1bShnZXRVbmlxdWVzKHgpKQp0cmFjayA8LSBjYmluZChvdXQsIAogICAgICAgICAgICAgICBzYXBwbHkoZGFkYUZzLCBnZXROKSwgCiAgICAgICAgICAgICAgIHJvd1N1bXMoc2VxdGFiLm5vY2hpbSkpCmNvbG5hbWVzKHRyYWNrKSA8LSBjKCJpbnB1dCIsICJmaWx0ZXJlZCIsICJkZW5vaXNlZEYiLCAgCiAgICAgICAgICAgICAgICAgICAgICJub25jaGltIikKcm93bmFtZXModHJhY2spIDwtIHNhbXBsZS5uYW1lcwp0cmFjayAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBtdXRhdGUocG9yY2VudF9maWx0ID0gMTAwLShmaWx0ZXJlZC9pbnB1dCoxMDApKQpgYGAKClZlbW9zIHF1ZSBlbiBlbCBmaWx0cmFkbyBzZSBkZXNjYXJ0YSBsYSBtYXlvciBwcm9wb3JjacOzbiBkZSBsb3MgcmVhZHMsIGVudHJlIDE5IHkgNTYlIGRlIGxvcyByZWFkcyBkZSBwYXJ0aWRhLgoKIyMgQXNpZ25hY2nDs24gdGF4b27Ds21pY2EKClBhcmEgbGEgYXNpZ25hY2nDs24gdGF4b27Ds21pY2EgdXRpbGl6YXJlbW9zIGxhIGJhc2UgZGUgZGF0b3MgKipVTklURSoqLCBvYnRlbmlkYSBkZXNkZSBodHRwczovL3VuaXRlLnV0LmVlLiBFc3RvIHNlIGxvZ3JhIGNvbiBsYSBmdW5jacOzbiBgYXNzaWduVGF4b25vbXkoKWA6CgoqKlJlcXVpZXJlOioqIAoKLSBzZWN1ZW5jaWFzIG5vIHF1aW3DqXJpY2FzICAKLSBhcmNoaXZvIGNvbiBsYXMgc2VjdWVuY2lhcyBkZSByZWZlcmVuY2lhIGVuIGZvcm1hdG8gZmFzdGEKCioqU2Ugb2J0aWVuZToqKgoKLSBVbmEgdGFibGEgcXVlIGNvbnRpZW5lIGNvbW8gbm9tYnJlcyBkZSBmaWxhcyBsYXMgc2VjdWVuY2lhcyB5IGVuIGxhcyBjb2x1bW5hcyBsYSB0YXhvbm9tw61hIGluZmVyaWRhLCBkb25kZSBlbiBjYWRhIGNvbHVtbmEgc2UgZGVmaW5lIGVsIG5pdmVsIGFsY2FuemFkbwoKYGBge3J9CnVuaXRlLnJlZiA8LSAiVU5JVEVEXzExLTIwMjAuZmFzdGEiCnRheGEgPC0gYXNzaWduVGF4b25vbXkoc2VxcyA9IHNlcXRhYi5ub2NoaW0sIAogICAgICAgICAgICAgICAgICAgICAgIHJlZkZhc3RhID0gdW5pdGUucmVmLCBtdWx0aXRocmVhZCA9IFRSVUUsIHRyeVJDID0gVFJVRSkKdGF4YS5wcmludCA8LSB0YXhhICAjIFJlbW92aW5nIHNlcXVlbmNlIHJvd25hbWVzIGZvciBkaXNwbGF5IG9ubHkKcm93bmFtZXModGF4YS5wcmludCkgPC0gTlVMTApgYGAKCmBgYHtyfQpoZWFkKHRheGEucHJpbnQpCmBgYAoKIyBBbsOhbGlzaXMgZGVsIG1ldGFnZW5vbWEgdXRpbGl6YW5kbyBQaHlsb3NlcQoKIyMgUmVzdW1lbiBkZSBsb3MgcGFzb3Mgc2VndWlkb3MgaGFzdGEgYWhvcmE6CgpBbnRlcyBkZSBjb250aW51YXIsIHZlcmVtb3MgZWwgcHJvY2VzbyBxdWUgaGVtb3MgdHJhbnNpdGFkbzoKCjEuIFByZXByb2Nlc2FtaWVudG8gZGUgcmVhZHMgIAogIDEuMS4gdXRpbGl6YW1vcyBRaWltZTIgcGFyYSBkZXNtdWx0aXBsZXhhciB5IGVsaW1pbmFyIGJhcmNvZGVzICAKICAxLjIuIHV0aWxpemFuZG8gZGFkYTI6ICAKICAgIDEuMi4xLiBlbGltaW5hbW9zIE5zICAKICAgIDEuMi4yLiBlbGltaW5hbW9zIHNlY3VlbmNpYXMgYWRhcHRhZG9yYXMgIAogICAgMS4yLjMuIGVsaW1pbmFtb3MgYmFzZXMgeSByZWFkcyBkZSBiYWphIGNhbGlkYWQgIAoyLiBQcm9jZXNhbWllbnRvIGRlIGxvcyByZWFkcyAgCiAgMi4xIFV0aWxpemFtb3MgZGFkYTIgIAogICAgMi4xLjEuIERlc3JlcGxpY2Ftb3MgcGFyYSBlbGltaW5hciBpbmZvcm1hY2nDs24gcmVkdW5kYW50ZSAgCiAgICAyLjEuMi4gVXRpbGl6YW1vcyBsYSBmdW5jacOzbiBkYWRhIHBhcmEgZWxpbWluYXIgcnVpZG8gZSBpbmZlcmlyIGxhcyBzZWN1ZW5jYXMgZGlmZXJlbnRlcwozLiBBc2lnbmFjacOzbiB0YXhvbsOzbWljYSAgCiAgMy4xIFV0aWxpemFtb3MgdW5hIGZ1bmNpw7NuIGRlIGRhZGEyIHBhcmEgYXNpZ25hciBjYWRhIHNlY3VlbmNpYSBhIHVuIHRheMOzbiwgdXRpbGl6YW5kbyB1bmEgYmFzZSBkZSBkYXRvcyBhcHJvcGlhZGEgY29tbyByZWZlcmVuY2lhCgojIyBPYnRlbmNpw7NuIGRlIGxhcyB0YWJsYXMgZGUgcmVzdWx0YWRvcyBkZSBsb3MgbWV0YWdlbm9tYXMKClV0aWxpemFyZW1vcyBmdW5jaW9uZXMgaW1wbGVudGFkYXMgZW4gZWwgcGFxdWV0ZSBwaHlsb3NlcSBwYXJhIG9idGVuZXIgbGFzIHRhYmxhcyBuZWNlc2FyaWFzIHBhcmEgdmlzdWFsaXphciBsb3MgcmVzdWx0YWRvcyBkZWwgbWV0YWdlbm9tYSB1dGlsaXphbmRvIFNoYW1hbjogaHR0cHM6Ly9zaGFtYW4ucGFzdGV1ci5mcgoKQWx0ZXJuYXRpdmFtZW50ZSBwdWVkZSB1dGlsaXphcnNlIHBoeWxvc2VxIHBhcmEgY29udGludWFyIGNvbiBsb3MgYW7DoWxpc2lzIHkgb2J0ZW5lciB2aXN1YWxpemFjaW9uZXMgZGVsIG1ldGFnZW5vbWEsIHBlcm8gU2hhbWFuIGVzIG3DoXMgc2VuY2lsbG8gZSBpbnR1aXRpdm8uCgojIyMgT2J0ZW5jacOzbiBkZSBsYSBPVFUgdGFibGU6CgpBbnRlcyBkZSBvYnRlbmVyIGxhIE9UVSB0YWJsZSwgdmFtb3MgYSBjb25zdHJ1aXIgdW4gZGF0YSBmcmFtZSBxdWUgY29udGllbmUgbG9zIG1ldGFkYXRvcyBkZSBjYWRhIG11ZXN0cmE6CgpgYGB7cn0Kc2FtcGxlcy5vdXQgPC0gcm93bmFtZXMoc2VxdGFiLm5vY2hpbSkKc2FtZGYgPC0gcmVhZC50YWJsZSgibWFwcGluZ2ZpbGVkYW5pZWxsYWl0c19jb3JyZWN0ZWQtMi50eHQiLCBoZWFkZXIgPSBUKQpzYW1kZiA8LSBzYW1kZlsxOjE4LGMoMSw1LDYpXQpyb3duYW1lcyhzYW1kZikgPC0gc2FtcGxlcy5vdXQKYGBgCgpBaG9yYSwgZXN0YW1vcyBlbiBjb25kaWNpb25lcyBkZSBjcmVhciB1biBvYmpldG8gcGh5bG9zZXEgcXVlIGNvbnRpZW5lIGxhIE9UVSB0YWJsZSwgbG9zIG1ldGFkYXRvcyB5IGxhIHRheGEgdGFibGU6CgpgYGB7cn0KcHMgPC0gcGh5bG9zZXEob3R1X3RhYmxlKHNlcXRhYi5ub2NoaW0sIHRheGFfYXJlX3Jvd3M9RkFMU0UpLCAKICAgICAgICAgICAgICAgc2FtcGxlX2RhdGEoc2FtZGYpLCAKICAgICAgICAgICAgICAgdGF4X3RhYmxlKHRheGEpKQpwcwpgYGAKClBhcmEgY29uc3RydWlyIGxhIHRheGEgdGFibGUgeSBsYSBPVFUgdGFibGUgdXRpbGl6YW1vcyBsYXMgZnVuY2lvbmVzIGB0YXhfdGFibGUoKWAgeSBgb3R1X3RhYmxlKClgIHJlc3BlY3RpdmFtZW50ZSBhIHBhcnRpciBkZWwgb2JqZXRvIHBoeWxvc2VxLgoKYGBge3J9Cm90dXRhYmxlIDwtIG90dV90YWJsZShwcykKb3R1LnRhYmxlX2RmIDwtIGFzLmRhdGEuZnJhbWUob3R1LnRhYmxlKQpyb3duYW1lcyhvdHUudGFibGVfZGYpIDwtIHNhbXBsZS5uYW1lcwoKdGF4YXRhYmxlIDwtIHRheF90YWJsZShwcykKdGF4YS50YWJsZV9kZiA8LSBhcy5kYXRhLmZyYW1lKHRheGF0YWJsZSkKYGBgCgpQYXJhIGV4cG9ydGFyIGxhcyB0YWJsYXMgcGFyYSB2aXN1YWxhciBsb3MgcmVzdWx0YWRvcyBlbiBTaGFtYW4gdXRpbGl6YW1vcyBsYSBmdW5jacOzbiBgd3JpdGUudGFibGVgIHNvYnJlIGxhcyB0YWJsYXMgcHJvZHVjaWRhcyBlbiBlbCBwYXNvIGFudGVyaW9yOgoKYGBge3J9CndyaXRlLnRhYmxlKHQob3R1LnRhYmxlX2RmKSwgZmlsZSA9ICJvdHVfdGFibGUudHN2Iiwgc2VwID0gIlx0IiwgcXVvdGUgPSBGKQp3cml0ZS50YWJsZSh0YXhhLnRhYmxlX2RmLCAidGF4YV90YWJsZS50c3YiLCBzZXAgPSAiXHQiLCBxdW90ZSA9IEYpCmBgYAoKIyMjIE9idGVuY2nDs24gZGUgbGEgdGFibGEgZGUgbWV0YWRhdG9zCgpTaGFtYW4gdGFtYmnDqW4gcmVxdWllcmUgdW5hIHRhYmxhIGRlIG1ldGFkYXRvcyBwYXJhIHJlYWxpemFyIGxhcyBjb21wYXJhY2lvbmVzIGVudHJlIG11ZXN0cmFzLiBQYXJhIGVzdG8gZGViZW1vcyBlbGltaW5hciBkZSBsYSB0YWJsYSBkZSBtZXRhZGF0b3MgdG9kb3MgbGFzIHZhcmlhYmxlcyBxdWUgY29udGVuZ2FuIGluZm9ybWFjacOzbiByZWR1bmRhbnRlLCBxdWUgY29udGVuZ2FuIGRhdG9zIHF1ZSBzZSBvYnRpZW5lbiBhIHBhcnRpciBkZSB0cmFuc2Zvcm1hY2lvbmVzIGxpbmVhbGVzIGRlIG90cmFzIHZhcmlhYmxlcywgbyBxdWUgbm8gc2UgdXRpbGljZW4gcGFyYSBjb250cmFzdGFyIGxhcyBtdWVzdHJhcy4gRW4gZXN0ZSBjYXNvIGVsaW1pbm8gbGFzIGNvbHVtbmFzIExpbmtlclByaW1lclNlcXVlbmNlLCBCYXJjb2RlU2VxdWVuY2UsIFRpZW1wbyB5IERlc2NyaXB0aW9uLCB5IGV4cG9ydG8gbGEgdGFibGEgcmVzdWx0YW50ZToKCmBgYHtyfQptZiA8LSByZWFkLnRhYmxlKCJtYXBwaW5nZmlsZWRhbmllbGxhaXRzX2NvcnJlY3RlZC0yLnR4dCIsIGhlYWRlciA9IFQpICU+JSAKICBzZWxlY3QoLUxpbmtlclByaW1lclNlcXVlbmNlLCAtQmFyY29kZVNlcXVlbmNlLCAtVGllbXBvLCAtRGVzY3JpcHRpb24pCndyaXRlLnRhYmxlKG1mLCBzZXAgPSAiXHQiLCAibWYudHN2IikKYGBgCgojIyMgUGFyYSB0ZXJtaW5hcgoKQXF1w60gZmluYWxpemEgZXN0ZSBwaXBlbGluZSwgZG9uZGUgc2Ugb2J0dXZpZXJvbjoKCi0gT1RVIHRhYmxlICAKLSB0YXhhIHRhYmxlICAKLSB0YWJsYSBkZSBtZXRhZGF0b3MKCkVzdGFzIHRhYmxhcywgZXhwb3J0YWRhcyBjb21vIGFyY2hpdm9zIGRlIHRleHRvIGNvbiBjb2x1bW5hcyBzZXBhcmFkYXMgcG9yIHRhYnVsYWNpb25lcyBwdWVkZSBzZXIgaW1wb3J0YWRhIGVuIFNoYW1hbiB5IGFuYWxpemFyc2UgeSB2aXN1YWxpemFyc2UgbG9zIG1ldGFnZW5vbWFzIGRlIGxhcyBtdWVzdHJhcywgYWRlbcOhcyBkZSByZWFsaXphciBjb21wYXJhY2lvbmVzLgo=